파이썬 (0523) 12주차

클래스
Author

김보람

Published

May 23, 2022

import numpy as np

클래스공부 4단계

Motivating Example

- 가위바위보

  • 방법1
class RPC2:
    def throw2(self):
        print(np.random.choice(['가위', '바위','보']))
a=RPC2()
a.throw2()
바위
  • 방법2
class RPC:
    def thorw(self, candidate):
        print(np.random.choice(candidate))
a=RPC()
a.thorw(['가위','바위','보'])
바위
  • 방법3
class RPC3:
    def __init__(self, candidate=['가위','바위','보']):
        self.candidate = candidate
    def throw3(self):
        print(np.random.choice(self.candidate))
a=RPC3()   # __init__ 는 암묵적으로 실행
a.throw3()
바위
  • 방법4
class RPC4:
    pass
b=RPC4()
def initt(b, candidate=['가위','바위','보']):
    b.candidate = candidate
initt(b)
b.candidate
['가위', '바위', '보']
def throww(b):
        print(np.random.choice(b.candidate))
throww(b)
가위
  • 방법5
# 위의 코드를 하나로 합치면..
class RPC4:
    def __init__(self, candidate=['가위','바위','보']):
        self.candidate = candidate
    def throww(self):
        print(np.random.choice(self.candidate))
a=RPC4()
a.throww()
가위

- 생각해보니까 throw는 choose + show 의 결합인 것 같다.

class RPC:
    def __init__(self, candidate=['가위','바위','보']):
        self.candidate = candidate
    def choose(self):
        self.actions = np.random.choice(self.candidate)
    def show(self):
        print(self.actions)
a=RPC()
a.actions()   # 지금은 정의되지 않음
AttributeError: 'RPC' object has no attribute 'actions'
a.choose()
a.actions  # 가위, 바위, 보 중 고른 결과가 나옴
'가위'
a.show()
가위

보충학습 : 위와 같은 코드

class _RPS: ## 시점1
    pass # <- 이렇게하면 아무기능이 없는 비어있는 클래스가 정의된다
_a = _RPS() ## 시점2
def _init(_a,candidate=['가위','바위','보']):
    _a.candidate = candidate 
_init(_a)
_a.actions ## 시점3
AttributeError: '_RPS' object has no attribute 'actions'
def _choose(_a): ## 시점4
    _a.actions = np.random.choice(_a.candidate)
_choose(_a)
_a.actions ## 시점5
'보'
def _show(_a): ## 시점6
    print(_a.actions)
_show(_a)

- 또 다른 인스턴스 b를 만들자. b는 가위만 낼 수 있다.

RPC?
Init signature: RPC(candidate=['가위', '바위', '보'])
Docstring:      <no docstring>
Type:           type
Subclasses:     
b=RPC(['가위'])
b.candidate
['가위']
b.choose()
b.show()
가위

- a,b의 선택들을 모아서 기록을 하고 싶다.

class RPS:
    def __init__(self,candidate=['가위','바위','보']):
        self.candidate = candidate
        self.actions = list() 
    def choose(self):
        self.actions.append(np.random.choice(self.candidate))
    def show(self):
        print(self.actions[-1])   # 지금 현재 내가 선택한 마지막만 보여줘!
a=RPS()
b=RPS(['가위'])
for i in range(5):
    a.choose()
    a.show()
바위
가위
바위
바위
보
a.actions   # 지금까지 뽑힌 히스토리들
['바위', '가위', '바위', '바위', '보']
for i in range(5):
    b.choose()
    b.show()
가위
가위
가위
가위
가위
b.actions
['가위', '가위', '가위', '가위', '가위']
a.candidate, a.actions
(['가위', '바위', '보'], ['바위', '가위', '바위', '바위', '보'])
b.candidate, b.actions
(['가위'], ['가위', '가위', '가위', '가위', '가위'])

- info라는 함수를 만들어서 a의 오브젝트가 가지고 있는 정보를 모두 보도록 하자.

(예비학습) 문자열 \n이 포함된다면?

'asdf\n1234'
'asdf\n1234'
print('asdf\n1234')
asdf
1234

예비학습 끝

class RPS: 
    def __init__(self,candidate=['가위','바위','보']):
        self.candidate = candidate
        self.actions = list() 
    def choose(self):
        self.actions.append(np.random.choice(self.candidate))
    def show(self):
        print(self.actions[-1])
    def info(self):
        print("낼 수 있는 패: {}\n기록: {}".format(self.candidate,self.actions))
a=RPS()
b=RPS(['가위'])
for i in range(5):
    a.choose()
    a.show()    
가위
바위
가위
보
보
for i in range(5):
    b.choose()
    b.show()
가위
가위
가위
가위
가위
a.info()
낼 수 있는 패: ['가위', '바위', '보']
기록: ['가위', '바위', '가위', '보', '보']
b.info()
낼 수 있는 패: ['가위']
기록: ['가위', '가위', '가위', '가위', '가위']

- 만들고 보니까 info와 print의 기능이 거의 비슷함 \(\to\) print(a)를 하면 a.info()와 동일한 효과를 내도록 만들 수 있을까?

- 안될 거 같다. 왜?

  • 안될 것 같은 이유 1: print 는 파이썬 내장기능, 내장기능을 우리가 맘대로 커스터마이징해서 쓰기는 어려울 것 같다.

  • 안될 것 같은 이유 2: 이유1이 해결된다고 해도 문제다. 다 꼬아져버려… 그럼 지금까지 사용했던 print()의 결과는 어떻게 되는가?

(예)

type(a)
__main__.RPS
a?
Type:        RPS
String form: <__main__.RPS object at 0x7f6565f29e10>
Docstring:   <no docstring>

- 그런데 a의 자료형(RPS자료형)에 해당하는 오브젝트들에 한정하여 print를 수정하는 방법이 가능하다면? (그럼 다른 오브젝트들은 수정된 print에 영향을 받지 않음)

__str___

- 관찰1: 현재 print(a)의 결과는 아래와 같다.

print(a)
<__main__.RPS object at 0x7f6565f29e10>
print([1,2,3])
[1, 2, 3]
  • a는 RPS클래스에서 만든 오브젝트이며 a가 저장된 메모리 주소는 0x7f6565f29e10라는 의미

- 관찰2: a에는 __str__ 이 있다.

dir(a)   # a + _ + tab을 누르면 숨겨진 메소드들이 나온다.
['__class__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__le__',
 '__lt__',
 '__module__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__',
 'actions',
 'candidate',
 'choose',
 'info',
 'show']
set(dir(a)) & {'__str__'}
{'__str__'}
a.__str__
<method-wrapper '__str__' of RPS object at 0x7f6565f29e10>

이것을 함수처럼 사용하니까 아래와 같이 된다.

a.__str__()
'<__main__.RPS object at 0x7f6565f29e10>'

?? print(a)를 해서 나오는 문자열이 리턴된다..

print(a.__str__()) # 이거 print(a)를 실행한 결과와 같다?
<__main__.RPS object at 0x7f6565f29e10>

- 생각: 만약 내가 a.__str__() 라는 함수를 재정의하여 리턴값을 boram hahaha로 바꾸게 되면 print(a)해서 나오는 결과는 어떻게 될까? (해커???)

(예비학습)

def f():
    print('adsf')
f()
adsf
def f():
    print('boram hahaha')
f()
boram hahaha

이런식으로 함수가 이미 정의되어 있더라도, 내가 나중에 덮어씌우면 그 함수의 기능을 다시 정의한다.

(해킹시작)

class RPS: 
    def __init__(self,candidate=['가위','바위','보']):
        self.candidate = candidate
        self.actions = list() 
    def choose(self):
        self.actions.append(np.random.choice(self.candidate))
    def show(self):
        print(self.actions[-1])
    def __str__(self):
        return 'boram hahaha'
    def info(self):
        print("낼 수 있는 패: {}\n기록: {}".format(self.candidate,self.actions))
a=RPS()
print(a)
boram hahaha
print(a.__str__())
boram hahaha
# 다른건 다 변함이 없음
a.choose()
a.show()
가위
a.actions
['가위']
a.info()
낼 수 있는 패: ['가위', '바위', '보']
기록: ['가위']

- __str__의 리턴값을 info에서 타이핑했던 문자열로 재정의 한다면?

class RPS: 
    def __init__(self,candidate=['가위','바위','보']):
        self.candidate = candidate
        self.actions = list() 
    def choose(self):
        self.actions.append(np.random.choice(self.candidate))
    def show(self):
        print(self.actions[-1])
    def __str__(self):
        return "낼 수 있는 패: {}\n기록: {}".format(self.candidate,self.actions)
a=RPS()
print(a)
낼 수 있는 패: ['가위', '바위', '보']
기록: []
a.choose()
a.show()
바위
print(a)
낼 수 있는 패: ['가위', '바위', '보']
기록: ['보', '바위']

파이썬의 비밀2

- print(a)print(a.__str__()) 는 같은 문법이다.

- 참고로 a.__Str__()str(a) 도 같은 문법

a.__str__()
"낼 수 있는 패: ['가위', '바위', '보']\n기록: ['보', '바위']"
str(a)
"낼 수 있는 패: ['가위', '바위', '보']\n기록: ['보', '바위']"

- 지금까지 썼던 기능들 확인!

(예제1)

a=[1,2,3]
print(a)
[1, 2, 3]
a.__str__()
'[1, 2, 3]'
str(a)
'[1, 2, 3]'

(예제2)

a={1,2,3}
print(a)
{1, 2, 3}
a.__str__()
'{1, 2, 3}'
str(a)
'{1, 2, 3}'

(예제3)

a=np.array(1)
a.shape
()
type(a.shape)
tuple
print(a.shape)
()
a.shape.__str__()
'()'
str(a.shape)
'()'

(예제4)

a = range(10)
print(a)
range(0, 10)
a.__str__()
'range(0, 10)'

(예제5)

a = np.arange(100).reshape(10,10)
print(a)
[[ 0  1  2  3  4  5  6  7  8  9]
 [10 11 12 13 14 15 16 17 18 19]
 [20 21 22 23 24 25 26 27 28 29]
 [30 31 32 33 34 35 36 37 38 39]
 [40 41 42 43 44 45 46 47 48 49]
 [50 51 52 53 54 55 56 57 58 59]
 [60 61 62 63 64 65 66 67 68 69]
 [70 71 72 73 74 75 76 77 78 79]
 [80 81 82 83 84 85 86 87 88 89]
 [90 91 92 93 94 95 96 97 98 99]]
a.__str__()
'[[ 0  1  2  3  4  5  6  7  8  9]\n [10 11 12 13 14 15 16 17 18 19]\n [20 21 22 23 24 25 26 27 28 29]\n [30 31 32 33 34 35 36 37 38 39]\n [40 41 42 43 44 45 46 47 48 49]\n [50 51 52 53 54 55 56 57 58 59]\n [60 61 62 63 64 65 66 67 68 69]\n [70 71 72 73 74 75 76 77 78 79]\n [80 81 82 83 84 85 86 87 88 89]\n [90 91 92 93 94 95 96 97 98 99]]'
str(a)
'[[ 0  1  2  3  4  5  6  7  8  9]\n [10 11 12 13 14 15 16 17 18 19]\n [20 21 22 23 24 25 26 27 28 29]\n [30 31 32 33 34 35 36 37 38 39]\n [40 41 42 43 44 45 46 47 48 49]\n [50 51 52 53 54 55 56 57 58 59]\n [60 61 62 63 64 65 66 67 68 69]\n [70 71 72 73 74 75 76 77 78 79]\n [80 81 82 83 84 85 86 87 88 89]\n [90 91 92 93 94 95 96 97 98 99]]'

__repr__

- 생각해보니까 print를 해서 원하는 정보를 확인하는 건 아니었음

a=[1,2,3]
a
[1, 2, 3]
print(a)   # print(a.__str__()) + enter => a + enter 와 같은 효과?
[1, 2, 3]

- a + 엔터를 하면 print(a) + 엔터를 하는 것과 같은 효과인가?

(반례)

a=np.array([1,2,3,4]).reshape(2,2)
a
array([[1, 2],
       [3, 4]])
print(a)
[[1 2]
 [3 4]]

- a + 엔터를 하면 print(a) + 엔터가 다른 경우도 있다. \(\to\) 서로 다른 숨겨진 기능이 잇다! \(\to\) 결론 : 그 기능은 __repr__에 저장되어 있다.

class RPS: 
    def __init__(self,candidate=['가위','바위','보']):
        self.candidate = candidate
        self.actions = list() 
    def choose(self):
        self.actions.append(np.random.choice(self.candidate))
    def show(self):
        print(self.actions[-1])
    def __repr__(self):
        return "낼 수 있는 패: {}\n기록: {}".format(self.candidate,self.actions)
a=RPS()
a  # print(a.__repr__())
낼 수 있는 패: ['가위', '바위', '보']
기록: []

- 그럼 지금까지 한것은?

a = np.array([1,2,3])
a
array([1, 2, 3])
print(a)
[1 2 3]
a.__repr__()
'array([1, 2, 3])'
a.__str__()
'[1 2 3]'

파이썬의 비밀3

- 대화형 콘솔에서 오브젝트 이름 + 엔터 를 쳐서 나오는 출력은 __repr__ 의 결과와 연관 있다.

a = np.array(range(10000)).reshape(100,100)
a
array([[   0,    1,    2, ...,   97,   98,   99],
       [ 100,  101,  102, ...,  197,  198,  199],
       [ 200,  201,  202, ...,  297,  298,  299],
       ...,
       [9700, 9701, 9702, ..., 9797, 9798, 9799],
       [9800, 9801, 9802, ..., 9897, 9898, 9899],
       [9900, 9901, 9902, ..., 9997, 9998, 9999]])
a.__repr__()
'array([[   0,    1,    2, ...,   97,   98,   99],\n       [ 100,  101,  102, ...,  197,  198,  199],\n       [ 200,  201,  202, ...,  297,  298,  299],\n       ...,\n       [9700, 9701, 9702, ..., 9797, 9798, 9799],\n       [9800, 9801, 9802, ..., 9897, 9898, 9899],\n       [9900, 9901, 9902, ..., 9997, 9998, 9999]])'

- 참고로 a.__repr__() 은 representation의 약자인데, repr(a)와 같다.

주피터 노브북의 비밀 (__repr__html__)

- 요즘에는 IDE의 발전에 따라서 오브젝트이름+엔터 칠때 나오는 출력의 형태도 다양해지고 있다.

import pandas as pd
df = pd.DataFrame({'a':[1,2,3],'b':[2,3,4]})
df
a b
0 1 2
1 2 3
2 3 4
  • 예쁘게 나온다.

- 그런데? print(df.__repr__())의 결과가 조금 다르게 나온당

print(df.__repr__())
   a  b
0  1  2
1  2  3
2  3  4

- print(df.__repr__()) 는 예전 검은화면에서 코딩할 때가 나오는 출력임

Python 3.10.2 | packaged by conda-forge | (main, Feb  1 2022, 19:28:35) [GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
> >> import pandas as pd 
>>> df = pd.DataFrame({'a':[1,2,3],'b':[2,3,4]})>>> df
   a  b
0  1  2
1  2  3
2  3  4
>>>

- 주피터에서는? “오브젝트이름+엔터”치면 HTML(df._repr_html_())이 실행되고, _repr_html_()이 정의되어 있지 않으면 print(df.__repr__())이 실행된다.

df._repr_html_()
'<div>\n<style scoped>\n    .dataframe tbody tr th:only-of-type {\n        vertical-align: middle;\n    }\n\n    .dataframe tbody tr th {\n        vertical-align: top;\n    }\n\n    .dataframe thead th {\n        text-align: right;\n    }\n</style>\n<table border="1" class="dataframe">\n  <thead>\n    <tr style="text-align: right;">\n      <th></th>\n      <th>a</th>\n      <th>b</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <th>0</th>\n      <td>1</td>\n      <td>2</td>\n    </tr>\n    <tr>\n      <th>1</th>\n      <td>2</td>\n      <td>3</td>\n    </tr>\n    <tr>\n      <th>2</th>\n      <td>3</td>\n      <td>4</td>\n    </tr>\n  </tbody>\n</table>\n</div>'
  • html코드
from IPython.core.display import HTML
HTML('<div>\n<style scoped>\n    .dataframe tbody tr th:only-of-type {\n        vertical-align: middle;\n    }\n\n    .dataframe tbody tr th {\n        vertical-align: top;\n    }\n\n    .dataframe thead th {\n        text-align: right;\n    }\n</style>\n<table border="1" class="dataframe">\n  <thead>\n    <tr style="text-align: right;">\n      <th></th>\n      <th>a</th>\n      <th>b</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <th>0</th>\n      <td>1</td>\n      <td>2</td>\n    </tr>\n    <tr>\n      <th>1</th>\n      <td>2</td>\n      <td>3</td>\n    </tr>\n    <tr>\n      <th>2</th>\n      <td>3</td>\n      <td>4</td>\n    </tr>\n  </tbody>\n</table>\n</div>')
a b
0 1 2
1 2 3
2 3 4
HTML(df._repr_html_())
a b
0 1 2
1 2 3
2 3 4

- 물론 df._repr_html_() 함수가 내부적으로 있어도 html이 지원되지 않는 환경이라면 print(__repr__())이 내부적으로 수행된다.

__repr____str__의 우선적용 순위

(예제1)

class RPS: 
    def __init__(self,candidate=['가위','바위','보']):
        self.candidate = candidate
        self.actions = list() 
    def choose(self):
        self.actions.append(np.random.choice(self.candidate))
    def show(self):
        print(self.actions[-1])
    def __repr__(self):
        return "낼 수 있는 패: {}\n기록: {}".format(self.candidate,self.actions)
a=RPS()
a
낼 수 있는 패: ['가위', '바위', '보']
기록: []
a.__repr__()
"낼 수 있는 패: ['가위', '바위', '보']\n기록: []"
repr(a)
"낼 수 있는 패: ['가위', '바위', '보']\n기록: []"

- 여기까지는 상식수준의 결과. 아래 관찰하자

print(a) # print(a.__str__())
낼 수 있는 패: ['가위', '바위', '보']
기록: []
a.__str__()
"낼 수 있는 패: ['가위', '바위', '보']\n기록: []"
str(a)
"낼 수 있는 패: ['가위', '바위', '보']\n기록: []"

- __str__()은 건드린적이 없다…?

a.__repr__??
Signature: a.__repr__()
Docstring: Return repr(self).
Source:   
    def __repr__(self):
        return "낼 수 있는 패: {}\n기록: {}".format(self.candidate,self.actions)
File:      ~/Dropbox/coco/posts/python/<ipython-input-201-29baf6ff56bf>
Type:      method

얘는 건드림

a.__str__??
Signature:      a.__str__()
Call signature: a.__str__(*args, **kwargs)
Type:           method-wrapper
String form:    <method-wrapper '__str__' of RPS object at 0x7f6561614c10>
Docstring:      Return str(self).

얘는 안건드렸는디..

(예제2)

class RPS: 
    def __init__(self,candidate=['가위','바위','보']):
        self.candidate = candidate
        self.actions = list() 
    def choose(self):
        self.actions.append(np.random.choice(self.candidate))
    def show(self):
        print(self.actions[-1])
    def __str__(self):
        return "낼 수 있는 패: {}\n기록: {}".format(self.candidate,self.actions)
a=RPS()
print(a)
낼 수 있는 패: ['가위', '바위', '보']
기록: []
a
<__main__.RPS at 0x7f6561429950>
a.__str__()
"낼 수 있는 패: ['가위', '바위', '보']\n기록: []"
a.__repr__()
'<__main__.RPS object at 0x7f6561429950>'
a.__str__??
Signature: a.__str__()
Docstring: Return str(self).
Source:   
    def __str__(self):
        return "낼 수 있는 패: {}\n기록: {}".format(self.candidate,self.actions)
File:      ~/Dropbox/coco/posts/python/<ipython-input-214-cd2a21868510>
Type:      method
a.__repr_??
Object `a.__repr_` not found.

(예제3)

class RPS: 
    def __init__(self,candidate=['가위','바위','보']):
        self.candidate = candidate
        self.actions = list() 
    def choose(self):
        self.actions.append(np.random.choice(self.candidate))
    def show(self):
        print(self.actions[-1])
    def __repr__(self):
        return "haha"
    def __str__(self):
        return "낼 수 있는 패: {}\n기록: {}".format(self.candidate,self.actions)
a=RPS()
a
haha
print(a)
낼 수 있는 패: ['가위', '바위', '보']
기록: []

- __str____repr__ 을 건드리지 않고 출력결과를 바꾸고 싶다면?

class RPS: 
    def __init__(self,candidate=['가위','바위','보']):
        self.candidate = candidate
        self.actions = list() 
    def choose(self):
        self.actions.append(np.random.choice(self.candidate))
    def show(self):
        print(self.actions[-1])
    def _repr_html_(self):
        html_str = """
        낼 수 있는 패: {} <br/> 
        기록: {}
        """
        return html_str.format(self.candidate,self.actions)
a=RPS()
a
낼 수 있는 패: ['가위', '바위', '보']
기록: []
print(a)
<__main__.RPS object at 0x7f6561534410>
str(a)
'<__main__.RPS object at 0x7f6561534410>'
repr(a)
'<__main__.RPS object at 0x7f6561534410>'
for i in range(5):
    a.choose()
    a.show()
바위
바위
바위
가위
보
a
낼 수 있는 패: ['가위', '바위', '보']
기록: ['바위', '바위', '바위', '가위', '보']